package Server;

import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ComunicatorServer { 
	private static final int REQUEST_PORT = 3000;  
	private static final int MESSAGE_PORT = 3001;
	private static final int SEARCH_PORT = 3002;
	private static final int LOGIN_WORKERS = 2;  //amount of threads handling login connections
	private static final int MESSAGE_WORKERS = 6;  //amount of threads handling incoming messages
	private static final int SEARCH_WORKERS = 3;  //amount of threads handling search requests
	
	private List<Socket> searchConnections;
	private List<Socket> requestConnections;
	private List<Socket> messageConnections;
	private Map<Long, ClientHandler> loggedUsers;

	public ComunicatorServer() {
		searchConnections = new ArrayList<Socket>();
		requestConnections = new ArrayList<Socket>();
		messageConnections = new ArrayList<Socket>();
		loggedUsers = new HashMap<Long, ClientHandler>();
	}
	
	//For testing
	public static void main(String[] args) {
		ComunicatorServer s = new ComunicatorServer();
		s.init();
		System.out.println("Server: Server is ready.");
	}
	
	public void init() {
		initSearchHandler();
		initLoginHandler();
		initMessageHandler();
	}

	private void initMessageHandler() {
		MessageListener ml = new MessageListener(MESSAGE_PORT);
		ml.start();
	}
	
	private void initSearchHandler() {
		SearchListener sl = new SearchListener(SEARCH_PORT);
		sl.start();
	}
	private void initLoginHandler() {
		RequestListener loginListener= new RequestListener(REQUEST_PORT);
		loginListener.start();
	}
	
	/*
	 * Adds new login connection to pool which is handled by worker threads
	 */
	private void processRequest(Socket requestToHandle) {
		synchronized (requestConnections) {
			requestConnections.add(requestConnections.size(), requestToHandle);
			requestConnections.notifyAll();
		}
	}
	
	/*
	 * Adds new message connection to pool which is handled by worker threads
	 */
	private void processMessage(Socket requestToHandle) {
		synchronized (messageConnections) {
			messageConnections.add(messageConnections.size(), requestToHandle);
			messageConnections.notifyAll();
		}
	}
	
	/*
	 * Adds new message connection to pool which is handled by worker threads
	 */
	private void processSearch(Socket requestToHandle) {
		synchronized (searchConnections) {
			searchConnections.add(searchConnections.size(), requestToHandle);
			searchConnections.notifyAll();
		}
	}
	
	/*
	 * Login listener is accepting incoming requests and putting them into pool
	 */
	private class RequestListener extends Thread {
		private int listenPort = 3000;
		
		public RequestListener(int listenPort) {
			super();
			this.listenPort = listenPort;
		}

		@Override
		public void run() {
			initLoginHandlers();
			acceptConnections();
		}
		
		/*
		 * Initialize workers to handle incoming login requests
		 */
		private void initLoginHandlers() {
			for (int i = 0; i < LOGIN_WORKERS; i++) {
				Thread loginHandler = new Thread(
						new LoginHandler(requestConnections, loggedUsers));
				loginHandler.start();
			}
		}
		
		public void acceptConnections() {
			try {
				ServerSocket server = new ServerSocket(listenPort, 50);
				Socket incomingConnection = null;
				while (true) {
					incomingConnection = server.accept();
					handleConnection(incomingConnection);
				}
			} catch (BindException e) {
				System.out.println("Unable to bind to port " + listenPort);
			} catch (IOException e) {
				System.out.println("Unable to instantiate a ServerSocket on port: "
						+ listenPort);
			}
		}

		protected void handleConnection(Socket connectionToHandle) {
			processRequest(connectionToHandle);
		}
	}
	
	/*
	 * Message listener is accepting incoming messages and putting them into pool
	 */
	private class MessageListener extends Thread {

		private int listenPort;
		
		public MessageListener(int listenPort) {
			super();
			this.listenPort = listenPort;
		}

		@Override
		public void run() {
			init();
			acceptConnections();
		}
		
		/*
		 * Initialize workers to handle incoming messages
		 */
		private void init() {
			for (int i = 0; i < MESSAGE_WORKERS; i++) {
				Thread messageWorker = new Thread(
						new MessageWorker(messageConnections, loggedUsers));
				messageWorker.start();
			}
		}
		
		public void acceptConnections() {
			try {
				ServerSocket server = new ServerSocket(listenPort, 50);
				Socket incomingConnection = null;
				while (true) {
					incomingConnection = server.accept();
					handleConnection(incomingConnection);
				}
			} catch (BindException e) {
				System.out.println("Unable to bind to port " + listenPort);
			} catch (IOException e) {
				System.out.println("Unable to instantiate a ServerSocket on port: "
						+ listenPort);
			}
		}

		protected void handleConnection(Socket connectionToHandle) {
			processMessage(connectionToHandle);
		}
	}
	
	private class SearchListener extends Thread {
		
		private int listenPort;
		
		public SearchListener(int listenPort) {
			super();
			this.listenPort = listenPort;
		}

		@Override
		public void run() {
			init();
			acceptConnections();
		}
		
		/*
		 * Initialize workers to handle incoming messages
		 */
		private void init() {
			for (int i = 0; i < SEARCH_WORKERS; i++) {
				Thread messageWorker = new Thread(
						new SearchHandler(searchConnections));
				messageWorker.start();
			}
		}
		
		public void acceptConnections() {
			try {
				ServerSocket server = new ServerSocket(listenPort, 50);
				Socket incomingConnection = null;
				while (true) {
					incomingConnection = server.accept();
					handleConnection(incomingConnection);
				}
			} catch (BindException e) {
				System.out.println("Unable to bind to port " + listenPort);
			} catch (IOException e) {
				System.out.println("Unable to instantiate a ServerSocket on port: "
						+ listenPort);
			}
		}

		protected void handleConnection(Socket connectionToHandle) {
			processSearch(connectionToHandle);
		}
	}
}